#[cfg(test)]
pub mod test_closure {
    use std::{thread, time::Duration};


    /* 什么是闭包（closure): 可以捕获其所在环境的匿名函数。
    ﹣是匿名函数
    ﹣可在一个地方创建闭包，然后在另一个上下文中调用闭包来完成运算
    ﹣保存为变量、作为参数
    ﹣可从其定义的作用域捕获值
     */
    
    #[test]
    fn clo01() {
        gen_work(15, 16); 
        gen_work(15, 18); 
        gen_work(32, 16); 
        gen_work(32, 18); 
    }

    fn gen_work(inten: u32, rand_num: u32) {
        let expensive_clos = |num| {
            println!("gen_workout expensive_clos...");
            thread::sleep(Duration::from_secs(1));
            num
        };
        if inten < 32 {
            println!("<32 1 {}", expensive_clos(inten));
            println!("<32 2 {}", expensive_clos(inten));
        } else {
            if rand_num == 16 {
                println!("rand_num == 16");
            } else {
                println!("rand_num != 16: {}", expensive_clos(inten));
            }
        }
    }

    /* 闭包的类型推断 
    - 闭包不要求标注参数和返回值的类型
    - 闭包通常很短小，只在狭小的上下文中工作，编译器通常能推断出类型
    - 可以手动添加类型标注
        let c = |num:u32| -> u32 {
            num
        }
    ·注意：闭包的定义最终只会为参数／返回值推断出唯一具体的类型
     */
    /* 函数和闭包的定义语法
    fn add_one_v1    (x: u32) -> u32 { x + 1 }
    let add_one_v2 = |x: u32| -> u32 { x + 1 };
    let add_one_v2 = |x|             { x + 1 };
    let add_one_v2 = |x|               x + 1  ;
     */


    /** 继续解决13.1中“运动计划”程序的问题
    ·另一种解决方案:
    ·创建一个struct，它持有闭包及其调用结果。
        - 只会在需要结果时才执行该闭包
        - 可缓存结果
    ·这个模式通常叫做记忆化(memoization)或延迟计算(lazy evaluation)
    */

    #[test]
    fn clo02() {
        let c = |x| x;
        println!("c1 = {}", c(String::from("c1 value")));
        // 这里报错
        // println!("c2 = {}", c(2));
    }

    /* 如何让struct持有闭包
    ·struct 的定义需要知道所有字段的类型
        - 需要指明闭包的类型
    ·每个闭包实例都有自己唯一的匿名类型，即使两个闭包签名完全一样。
    ·所以需要使用：泛型和Trait Bound（第10章）
     */

    /* Fn Trait: 由标准库提供, 所有的闭包都至少实现了以下 trait之一
    -Fn
    -FnMut
    -FnOnce
    */

    struct Cacher<T>
    where 
        T: Fn(u32) -> u32,
    {
        calc: T,
        val: Option<u32>
    }

    impl <T> Cacher<T> 
    where 
        T: Fn(u32) -> u32,
    {
        fn new(calc: T) -> Cacher<T> {
            Cacher{calc, val: None}
        }

        fn value(&mut self, arg: u32) -> u32 {
            match self.val {
                Some(v) => v,
                None => {
                    let v = (self.calc)(arg);
                    self.val = Some(v);
                    v
                }
            }
        }
    }

    fn gen_work2(inten: u32, rand_num: u32) {
        let mut expensive_clos = Cacher::new(|num| {
            println!("gen_work2 expensive_clos...");
            thread::sleep(Duration::from_secs(1));
            num
        });
        if inten < 32 {
            println!("<32 1 {}", expensive_clos.value(inten));
            println!("<32 2 {}", expensive_clos.value(inten));
        } else {
            if rand_num == 16 {
                println!("rand_num == 16");
            } else {
                println!("rand_num != 16: {}", expensive_clos.value(inten));
            }
        }
    }

    #[test]
    fn clo03() {
        let mut c = Cacher::new(|x| x);
        println!("c1 = {}", c.value(1));
        println!("c2 = {}", c.value(2));
    }

    /* 使用缓存器（Cacher）实现的限制 
    1. Cacher实例假定针对不同的参数arg,value方法总会得到同样的值。
        ﹣可以使用HashMap代替单个值：
            key: arg参数
            value：执行闭包的结果
    2. 只能接收一个 u32类型的参数和u32类型的返回值 
    */

    /** 
    闭包可以捕获他们所在的环境 
    ．闭包可以访问定义它的作用域内的变量，而普通函数则不能。
    ．会产生内存开销。
    */

    /* 闭包从所在环境捕获值的方式
    ．与函数获得参数的三种方式一样：
        1. 取得所有权：FnOnce
        2．可变借用：FnMut
        3．不可变借用：Fn
    ．创建闭包时，通过闭包对环境值的使用，Rust推断出具体使用哪个trait:
        ﹣所有的闭包都实现了FnOnce
        ﹣没有移动捕获变量的实现了FnMut
        ﹣无需可变访问捕获变量的闭包实现了Fn
    */
    #[test]
    fn clo04() {
        let (x, y) = (4, 4);

        let equal_to_x = |z: i32| z == x;
        println!("equal_to_x = {}", equal_to_x(y));

        // fn equal_to_x2(z: i32) -> bool {
            // z == x
        // };
        // println!("equal_to_x = {}", equal_to_x2(y));
    }

    /* move 关键字
    ．在参数列表前使用move关键字，可以强制闭包取得它所使用的环境值的所有权
        ﹣当将闭包传递给新线程以移动数据使其归新线程所有时，此技术最为有用。
    */

    #[test]
    fn clo05() {
        let x = vec![1, 2, 3];

        let equal_to_x = move |z| z == x;
        
        // println!("can use x? {:?}", x); // can`t

        let y = vec![1, 2, 3];

        println!("x == y -> {}", equal_to_x(y));

    }
    /* 最佳实践 
    ．当指定Fn trait bound之一时，首先用Fn，基于闭包体里的情况，如果需要FnOnce 或FnMut，编译器会再告诉你。
    */

}
